//
//  ElectronicNote.cpp
//  XML
//
//  Created by ling on 2024/7/18.
//

#include "ElectronicNote.h"
#include <cmath>

class ElectronicNote {
    
public:
    
    ElectronicNote() {
        isBeam = 0;
        isHigh = false;
        stemElement = nullptr;
        octaveElement = nullptr;
        stepElement = nullptr;
        alterElement = nullptr;
        pitchElement = nullptr;
        nodeElement = nullptr;
    }
    
    /// 符干方向，down（朝下），up（朝上），b4及以上，down，其他的up
    tinyxml2::XMLElement *stemElement;
    
    /// 位于哪个8度
    tinyxml2::XMLElement *octaveElement;
    
    /// 音阶（cdefgab）
    tinyxml2::XMLElement *stepElement;
    
    /// 数值为正表示升高音调，为负表示降低音调
    tinyxml2::XMLElement *alterElement;
    
    /// 是否是连杆，0:不是，1:开始，2:中间，3:结束
    int isBeam;
    
    /// 是否是高音轨
    bool isHigh;
    
    /// 音符里面音阶信息
    tinyxml2::XMLElement *pitchElement;
    
    tinyxml2::XMLElement *nodeElement;
                    
    int stepValue() {
        const char* stepKey = step();
        int alterValue = 0;
        if (this->alterElement != nullptr) {
            alterValue = atoi(alter());
        }
        if ((tinyxml2::XMLUtil::StringEqual("C", stepKey))) {
            return alterValue;
        }
        if ((tinyxml2::XMLUtil::StringEqual("D", stepKey))) {
            return 2 + alterValue;
        }
        if ((tinyxml2::XMLUtil::StringEqual("E", stepKey))) {
            return 4 + alterValue;
        }
        if ((tinyxml2::XMLUtil::StringEqual("F", stepKey))) {
            return 5 + alterValue;
        }
        if ((tinyxml2::XMLUtil::StringEqual("G", stepKey))) {
            return 7 + alterValue;
        }
        if ((tinyxml2::XMLUtil::StringEqual("A", stepKey))) {
            return 9 + alterValue;
        }
        if ((tinyxml2::XMLUtil::StringEqual("B", stepKey))) {
            return 11 + alterValue;
        }
        return alterValue;
    }

    void setStem(const char* value) {
        if (this->stemElement == nullptr) {
            return;
        }
        this->stemElement->SetText(value);
    }

    const char* step() {
        if (this->stepElement == nullptr) {
            return nullptr;
        }
        return this->stepElement->GetText();
    }

    const char* alter() {
        if (this->alterElement == nullptr) {
            return nullptr;
        }
        return this->alterElement->GetText();
    }

    const char* octave() {
        if (this->octaveElement == nullptr) {
            return nullptr;
        }
        return this->octaveElement->GetText();
    }

    int stem() {
        if (this->stemElement == nullptr) {
            return 0;
        }
        const char* stemValue = stemElement->GetText();
        if (tinyxml2::XMLUtil::StringEqual("up", stemValue)) {
            return 1;
        }
        if (tinyxml2::XMLUtil::StringEqual("down", stemValue)) {
            return -1;
        }
        return 0;
    }
    
    void configBeamValue(std::vector<tinyxml2::XMLElement *> beamList, std::vector<tinyxml2::XMLElement *> tiedList, std::vector<tinyxml2::XMLElement *> slurList) {
        int beamValue = 0;
        for (int i = 0; i < beamList.size(); i++) {
            beamValue = beamRealValue(beamValue, beamToValue(beamList[i]));
        }
        
        for (int i = 0; i < tiedList.size(); i++) {
            beamValue = beamRealValue(beamValue, tiedToValue(tiedList[i]));
        }
        
        for (int i = 0; i < slurList.size(); i++) {
            beamValue = beamRealValue(beamValue, tiedToValue(slurList[i]));
        }
        isBeam = beamValue;
    }
    
    int beamRealValue(int beamValue, int beam) {
        switch (beamValue) {
            case 0:
                return beam;
                
            case 1:
                return (beam == 1 || beam == 2) ? beam : beamValue;
                
            case 3:
                return (beam == 1 || beam == 2) ? 2 : beamValue;
                
            default:
                break;
        }
        return beamValue;
    }
    
    int beamToValue(tinyxml2::XMLElement *element) {
        if (element == nullptr) {
            return 0;
        }
        if (tinyxml2::XMLUtil::StringEqual(element->GetText(), "begin")) {
            return 1;
        }
        if (tinyxml2::XMLUtil::StringEqual(element->GetText(), "end")) {
            return 3;
        }
        return 2;
    }
    
    int tiedToValue(tinyxml2::XMLElement *element) {
        if (element == nullptr) {
            return 0;
        }
        if (tinyxml2::XMLUtil::StringEqual(element->Attribute("type"), "start")) {
            return 1;
        }
        if (tinyxml2::XMLUtil::StringEqual(element->Attribute("type"), "stop")) {
            return 3;
        }
        return 2;
    }

    ElectronicStepModel matchStepModel(int stepValue) {
        ElectronicStepModel stepModel;
        if (stepValue < 0) {
            stepValue = stepValue + 12;
        }
        if (stepValue > 12) {
            stepValue = stepValue % 12;
        }
        switch (stepValue) {
            case 0:
                return ElectronicStepModel {"B", "C", "D"};

            case 1:
                return ElectronicStepModel {"C", "", "D"};

            case 2:
                return ElectronicStepModel {"C", "D", "E"};

            case 3:
                return ElectronicStepModel {"D", "", "E"};

            case 4:
                return ElectronicStepModel {"D", "E", "F"};

            case 5:
                return ElectronicStepModel {"E", "F", "G"};

            case 6:
                return ElectronicStepModel {"F", "", "G"};

            case 7:
                return ElectronicStepModel {"F", "G", "A"};

            case 8:
                return ElectronicStepModel {"G", "", "A"};

            case 9:
                return ElectronicStepModel {"G", "A", "B"};

            case 10:
                return ElectronicStepModel {"A", "", "B"};

            case 11:
                return ElectronicStepModel {"A", "B", "C"};

            case 12:
                return ElectronicStepModel {"B", "C", "D"};

            default:
                break;
        }
        return stepModel;
    }

    void setStep(const char* value) {
        if (tinyxml2::XMLUtil::StringEqual("", value)) {
            if (this->stepElement != nullptr) {
                pitchElement->DeleteNode(this->stepElement);
            }
        }else {
            if (this->stepElement == nullptr) {
                this->stepElement = pitchElement->InsertNewChildElement("step");
            }
            this->stepElement->SetText(value);
        }
    }

    void setOctave(const char* value) {
        if (tinyxml2::XMLUtil::StringEqual("", value)) {
            if (this->octaveElement != nullptr) {
                pitchElement->DeleteNode(this->octaveElement);
            }
        }else {
            if (this->octaveElement == nullptr) {
                this->octaveElement = this->pitchElement->InsertNewChildElement("octave");
            }
            this->octaveElement->SetText(value);
        }
    }

    void setAlter(const char* value) {
        if (tinyxml2::XMLUtil::StringEqual("", value)) {
            if (this->alterElement != nullptr) {
                this->pitchElement->DeleteNode(this->alterElement);
            }
        }else {
            if (this->alterElement == nullptr) {
                this->alterElement = pitchElement->InsertAfterNewElement(this->stepElement, "alter");
            }
            this->alterElement->SetText(value);
        }
    }

    void transXML(int position) {
        int orderStepValue = stepValue() + position;
        
        int positionOctave = 0;
        if (orderStepValue < 0) {
            positionOctave = -1;
        }
        if (orderStepValue > 11) {
            positionOctave = 1;
        }
        
        const char* orderStep;
        const char* orderAlter;
        ElectronicStepModel stepModel = matchStepModel(orderStepValue);
        if (tinyxml2::XMLUtil::StringEqual("", stepModel.step)) {
            if (position == 3 || position == 5 || position == -2 || position == -4 || position == -6) {
                orderStep = stepModel.nextStep;
                orderAlter = "-1";
            }else {
                orderStep = stepModel.preStep;
                orderAlter = "1";
            }
        }else {
            orderStep = stepModel.step;
            orderAlter = "";
        }
        
        if (position == 1 && tinyxml2::XMLUtil::StringEqual("C", orderStep) && tinyxml2::XMLUtil::StringEqual("", orderAlter)) {
            orderStep = "B";
            orderAlter = "1";
            positionOctave = 0;
        }
        
        if (position == 1 && tinyxml2::XMLUtil::StringEqual("F", orderStep) && tinyxml2::XMLUtil::StringEqual("", orderAlter)) {
            orderStep = "E";
            orderAlter = "1";
        }
        
        if (position == 6 && tinyxml2::XMLUtil::StringEqual("F", orderStep) && tinyxml2::XMLUtil::StringEqual("", orderAlter)) {
            orderStep = "E";
            orderAlter = "1";
        }
        
        if (position == -6 && tinyxml2::XMLUtil::StringEqual("B", orderStep) && tinyxml2::XMLUtil::StringEqual("", orderAlter)) {
            orderStep = "C";
            orderAlter = "-1";
            positionOctave = 0;
        }
        
        setStep(orderStep);
        setAlter(orderAlter);
        
        int octaveValue = 0;
        if (this->octaveElement != nullptr) {
            octaveValue = atoi(octave()) + positionOctave;
        }
        setOctave(std::to_string(octaveValue).c_str());
        
        if (stem() == 0) {
            
        }else {
            if (octaveValue > 4) {
                setStem("down");
            }else if (octaveValue == 4 && tinyxml2::XMLUtil::StringEqual("B", orderStep)) {
                setStem("down");
            }else if (octaveValue == 4 && tinyxml2::XMLUtil::StringEqual("A", orderStep) && tinyxml2::XMLUtil::StringEqual("1", orderAlter)) {
                setStem("down");
            }else {
                setStem("up");
            }
        }
    }
    
};

const char* ElectronicAnalyzeTool::analyzeKey(const char* xmlPath) const {
    tinyxml2::XMLDocument xmlObj;
    tinyxml2::XMLError errCode = xmlObj.LoadFile(xmlPath);
    if (errCode != tinyxml2::XML_SUCCESS) {
        return "C";
    }
    tinyxml2::XMLElement* rootElement = xmlObj.RootElement();
    if (!rootElement) {
        return "C";
    }
    tinyxml2::XMLNode *partNode = rootElement->firstAppointNode("part");
    if (partNode != nullptr) {
        tinyxml2::XMLNode *measureNode = partNode->firstAppointNode("measure");
        tinyxml2::XMLNode *attributes = measureNode->firstAppointNode("attributes");
        tinyxml2::XMLNode *key = attributes->firstAppointNode("key");
        tinyxml2::XMLElement *fifthsElement = key->firstAppointNode("fifths")->ToElement();
//        major：大调      minor：小调
        int fifth = atoi(fifthsElement->GetText());
        for (int i = 0; i < this->fifthsList.size(); i++) {
            ElectronicFifthModel fifthM = this->fifthsList[i];
            if (fifthM.fifths == fifth) {
                return fifthM.fifth;
            }
        }
    }
    return "C";
}

int ElectronicAnalyzeTool::analyzeXML(const char* xmlPath, const char* savePath, int position) const{
    tinyxml2::XMLDocument xmlObj;
    tinyxml2::XMLError errCode = xmlObj.LoadFile(xmlPath);
    if (errCode != tinyxml2::XML_SUCCESS) {
        return 0;
    }
    tinyxml2::XMLElement* rootElement = xmlObj.RootElement();
    if (!rootElement) {
        return 0;
    }
    ElectronicFifthModel fifthModel = {6 , 0, "C", "0"};
    tinyxml2::XMLNode *partNode = rootElement->firstAppointNode("part");
    if (partNode != nullptr) {
        tinyxml2::XMLNode *measureNode = partNode->firstAppointNode("measure");
        tinyxml2::XMLNode *attributes = measureNode->firstAppointNode("attributes");
        tinyxml2::XMLNode *key = attributes->firstAppointNode("key");
        tinyxml2::XMLElement *fifthsElement = key->firstAppointNode("fifths")->ToElement();
        int fifth = atoi(fifthsElement->GetText());
        for (int i = 0; i < this->fifthsList.size(); i++) {
            ElectronicFifthModel fifthM = this->fifthsList[i];
            if (fifthM.fifths == fifth) {
                fifthModel = fifthM;
            }
        }
    }
    int fifthPosition = fifthModel.position + position;
    if (fifthPosition < 0) {
        fifthPosition = fifthPosition + 12;
    }
    if (fifthPosition > 12) {
        fifthPosition = fifthPosition % 12;
    }
    const char* fifths;
    for (int i = 0; i < this->fifthsList.size(); i++) {
        ElectronicFifthModel fifthM = this->fifthsList[i];
        if (fifthM.position == fifthPosition) {
            fifths = fifthM.fifthChar;
            break;
        }
    }
    
    std::vector<tinyxml2::XMLNode *> partList = rootElement->appointSibling("part");
    std::vector<tinyxml2::XMLNode *> notePartHighList;
    std::vector<tinyxml2::XMLNode *> notePartLowList;
    std::vector<const char *>simpleKeys = this->simpleKeyList[fifthModel.position];
    std::vector<const char *>orderSimplekeys = this->simpleKeyList[fifthPosition];
    for (int i = 0; i < partList.size(); i++) {
        tinyxml2::XMLNode* part = partList[i];
        std::vector<tinyxml2::XMLNode *> measureList = part->appointSibling("measure");
        tinyxml2::XMLNode *firstMeasure = measureList[0];
        tinyxml2::XMLNode *attributes = firstMeasure->firstAppointNode("attributes");
        tinyxml2::XMLNode *key = attributes->firstAppointNode("key");
        tinyxml2::XMLElement *fifthsElement = key->firstAppointNode("fifths")->ToElement();
        fifthsElement->SetText(fifths);
        for (int j = 0; j < measureList.size(); j++) {
            tinyxml2::XMLNode* measure = measureList[j];
            std::vector<tinyxml2::XMLNode *> _noteList;
            tinyxml2::XMLNode* childNode = measure->FirstChild();
            while (childNode) {
                if (tinyxml2::XMLUtil::StringEqual(childNode->Value(), "direction")) {
                    tinyxml2::XMLNode *directionTypeNode = childNode->firstAppointNode("direction-type");
                    if (directionTypeNode != nullptr) {
                        tinyxml2::XMLElement *wordsElement = directionTypeNode->FirstChildElement("words");
                        if (wordsElement != nullptr) {
                            const char* wordsText = wordsElement->GetText();
                            if (wordsText[0] >= 'A' && wordsText[0] <= 'Z') {
                                for (int k = 0; k < simpleKeys.size(); k++) {
                                    if (tinyxml2::XMLUtil::StringEqual(simpleKeys[k], wordsText)) {
                                        wordsElement->SetText(orderSimplekeys[k]);
                                        break;
                                    }
                                }
                            } else if (tinyxml2::XMLUtil::StringEqual("-", wordsText)) {
                                
                            } else {
                                int wordsValue = wordValue(wordsElement->GetText());
                                if (wordsValue != 0) {
                                    ElectronicStepModel wordModel = matchWordModel(wordsValue + position);
                                    int directType = 0;
                                    if (position == 1 || position == 2 || position == 4 || position == -1 || position == -3 || position == -5 || position == -6) {
                                        directType = 1;
                                    }
                                    if (position == 3 || position == 5 || position == -2 || position == -4) {
                                        directType = -1;
                                    }
                                    if (position == 1) {
                                        std::string result = "#";
                                        result += wordModel.preStep;
                                        wordsElement->SetText(result.c_str());
                                    }else {
                                        if (tinyxml2::XMLUtil::StringEqual("", wordModel.step)) {
                                            if (directType == 1) {
                                                std::string result = "#";
                                                result += wordModel.preStep;
                                                wordsElement->SetText(result.c_str());
                                            }else {
                                                std::string result = "b";
                                                result += wordModel.nextStep;
                                                wordsElement->SetText(result.c_str());
                                            }
                                        }else {
                                            std::string result = " ";
                                            result += wordModel.step;
                                            wordsElement->SetText(result.c_str());
                                        }
                                    }
                                }
                            }
                        }
                    }
                }else {
                    if (tinyxml2::XMLUtil::StringEqual(childNode->Value(), "note")) {
                        _noteList.push_back(childNode);
                    }
                }
                childNode = childNode->NextSibling();
            }
            for (int n = 0; n < _noteList.size(); n++) {
                tinyxml2::XMLNode* note = _noteList[n];
                if (note->firstAppointNode("pitch") != nullptr) {
                    if (i == 0) {
                        notePartHighList.push_back(note);
                    }
                    if (i == 1) {
                        notePartLowList.push_back(note);
                    }
                }
            }
        }
    }
    std::vector<ElectronicNote> nodeList;
    for (int i = 0; i < notePartHighList.size(); i++) {
        ElectronicNote note = ElectronicNote();
        tinyxml2::XMLNode *nodeXml = notePartHighList[i];
        note.nodeElement = nodeXml->ToElement();
        note.stemElement = nodeXml->FirstChildElement("stem");
        note.pitchElement = nodeXml->FirstChildElement("pitch");
        note.stepElement = note.pitchElement->FirstChildElement("step");
        note.alterElement = note.pitchElement->FirstChildElement("alter");
        note.octaveElement = note.pitchElement->FirstChildElement("octave");
        note.isHigh = true;
        tinyxml2::XMLNode *notations = nodeXml->FirstChildElement("notations");
        if (notations != nullptr) {
            note.configBeamValue(nodeXml->childElements("beam"), notations->childElements("tied"), notations->childElements("slur"));
        }else {
            note.configBeamValue(nodeXml->childElements("beam"), {}, {});
        }
        nodeList.push_back(note);
    }
    
    for (int i = 0; i < notePartLowList.size(); i++) {
        ElectronicNote note = ElectronicNote();
        tinyxml2::XMLNode *nodeXml = notePartLowList[i];
        note.nodeElement = nodeXml->ToElement();
        note.stemElement = nodeXml->FirstChildElement("stem");
        note.pitchElement = nodeXml->FirstChildElement("pitch");
        note.stepElement = note.pitchElement->FirstChildElement("step");
        note.alterElement = note.pitchElement->FirstChildElement("alter");
        note.octaveElement = note.pitchElement->FirstChildElement("octave");
        note.isHigh = true;
        tinyxml2::XMLNode *notations = nodeXml->FirstChildElement("notations");
        if (notations != nullptr) {
            note.configBeamValue(nodeXml->childElements("beam"), notations->childElements("tied"), notations->childElements("slur"));
        }else {
            note.configBeamValue(nodeXml->childElements("beam"), {}, {});
        }
        note.isHigh = false;
        nodeList.push_back(note);
    }
    
    std::vector<ElectronicNote> beamList;
    for (int i = 0; i < nodeList.size(); i++) {
        ElectronicNote node = nodeList[i];
        node.transXML(position);
        switch (node.isBeam) {
            case 1:
                {
                    beamList.push_back(node);
                }
                break;
                
            case 2:
            {
                beamList.push_back(node);
            }
                break;
                
            case 3:
            {
                beamList.push_back(node);
                int isUp = round(beamList.size() * 0.5) * (node.isHigh ? 1 : -1);
                for (int j = 0; j < beamList.size(); j++) {
                    ElectronicNote beam = beamList[j];
                    isUp = isUp + beam.stem();
                }
                for (int j = 0; j < beamList.size(); j++) {
                    ElectronicNote beam = beamList[j];
                    beam.setStem(isUp >= 0 ? "up" : "down");
                }
                beamList.clear();
            }
                break;
                
            default:
                break;
        }
    }
    tinyxml2::XMLError saveError = xmlObj.SaveFile(savePath);
    if (saveError == tinyxml2::XML_SUCCESS) {
        return 1;
    }
    return 0;
}

int ElectronicAnalyzeTool::wordValue(const char* word) const {
    char wordFirst = word[0];
    int extenValue = 0;
    if (wordFirst == '-') {
        return extenValue;
    }
    if (wordFirst == '#') {
        extenValue = 1;
    }
    if (wordFirst == 'b') {
        extenValue = -1;
    }
    switch (atoi(&word[std::strlen(word) - 1])) {
        case 1:
            return extenValue + 1;
            
        case 2:
            return extenValue + 3;
            
        case 3:
            return extenValue + 5;
            
        case 4:
            return extenValue + 6;
            
        case 5:
            return extenValue + 8;
            
        case 6:
            return extenValue + 10;
            
        case 7:
            return extenValue + 12;
            
        default:
            break;
    }
    return extenValue;
}

ElectronicStepModel ElectronicAnalyzeTool::matchWordModel(int stepValue) const {
    ElectronicStepModel stepModel;
    if (stepValue < 1) {
        stepValue = stepValue + 12;
    }
    if (stepValue > 12) {
        stepValue = stepValue % 12;
    }
    switch (stepValue) {
        case 1:
            return ElectronicStepModel {"7", "1", "2"};

        case 2:
            return ElectronicStepModel {"1", "", "2"};

        case 3:
            return ElectronicStepModel {"1", "2", "3"};

        case 4:
            return ElectronicStepModel {"2", "", "3"};

        case 5:
            return ElectronicStepModel {"2", "3", "4"};

        case 6:
            return ElectronicStepModel {"3", "4", "5"};

        case 7:
            return ElectronicStepModel {"4", "", "5"};

        case 8:
            return ElectronicStepModel {"4", "5", "6"};

        case 9:
            return ElectronicStepModel {"5", "", "6"};

        case 10:
            return ElectronicStepModel {"5", "6", "7"};

        case 11:
            return ElectronicStepModel {"6", "", "7"};

        case 12:
            return ElectronicStepModel {"6", "7", "1"};

        default:
            break;
    }

    return stepModel;
}
